#include "PreCompile.hpp"
#include "PreProcDOM.hpp"
#include "Macro.hpp"
#include "../Shared/TmpAllocator.hpp"
#include "TokenizerInput.hpp"
#include "IncludeDirective.hpp"
#include "FileParser.hpp"

namespace CPreProc
{
  typedef std::string ParseError;

  ////////
  // Group
  void Group::process(PreProcessorContext &context) const
  {
    for(int i = 0;i < (int)group_parts.size();i++)
    {
      group_parts[i]->process(context);
    }
  }

  //////////
  // DefineDirective
  DefineDirective::DefineDirective(Tokenizer &tokenizer,bool directive_read)
  {
    macro = Macro::parse(tokenizer,directive_read);
  }

  Glib::RefPtr<DefineDirective> DefineDirective::parse(Tokenizer &tokenizer,bool directive_read)
  {
    return Glib::RefPtr<DefineDirective>(new DefineDirective(tokenizer,directive_read));
  }

  Glib::RefPtr<DefineDirective> DefineDirective::parse(const Glib::ustring &line,bool directive_read)
  {
    Tokenizer tokenizer(line.c_str());
    return Glib::RefPtr<DefineDirective>(new DefineDirective(tokenizer,directive_read));
  }

  void DefineDirective::process(PreProcessorContext &context) const
  {
    context.define_macro(macro);
  }

  ////////////
  // UndefDirective
  UndefDirective::UndefDirective(Tokenizer &tokenizer,bool directive_read)
  {
    assert(!directive_read);      // not implemented yet

    tokenizer.skip_required_whitespace();
    Token identifier = tokenizer.read_identifier();
    macro_name = identifier.to_string();

    tokenizer.skip_optional_whitespace();
    if(!tokenizer.end_of_data())
      throw ParseError("Unexpected token. New line expected.");
  }

  Glib::RefPtr<UndefDirective> UndefDirective::parse(Tokenizer &tokenizer,bool directive_read)
  {
    return Glib::RefPtr<UndefDirective>(new UndefDirective(tokenizer,directive_read));
  }

  Glib::RefPtr<UndefDirective> UndefDirective::parse(const Glib::ustring &line,bool directive_read)
  {
    Tokenizer tokenizer(line.c_str());
    return Glib::RefPtr<UndefDirective>(new UndefDirective(tokenizer,directive_read));
  }

  void UndefDirective::process(PreProcessorContext &context) const
  {
    context.undef_macro(macro_name);
  }

  const Glib::ustring& UndefDirective::get_macro_name() const
  {
    return macro_name;
  }

  void UndefDirective::set_macro_name(const Glib::ustring &name)
  {
    macro_name = name;
  }

  /////////
  // ErrorDirective
  ErrorDirective::ErrorDirective(const Glib::ustring &message)
    : message(message)
  {
  }

  ErrorDirective::ErrorDirective(Tokenizer &tokenizer,bool directive_read)
  {
    assert(directive_read);   // not implemented yet

    message = tokenizer.read_to_end().to_string();
  }

  Glib::RefPtr<ErrorDirective> ErrorDirective::parse(Tokenizer &tokenizer,bool directive_read)
  {
    return Glib::RefPtr<ErrorDirective>(new ErrorDirective(tokenizer,directive_read));
  }

  void ErrorDirective::process(PreProcessorContext &context) const
  {
    context.report_error(ErrorSource_Directive,message);
  }

  /////////
  // WarningDirective
  WarningDirective::WarningDirective(const Glib::ustring &message)
    : message(message)
  {
  }

  void WarningDirective::process(PreProcessorContext &context) const
  {
    context.report_warning(WarningSource_Directive,message);
  }

  //////////
  // LineDirective
  LineDirective::LineDirective(int line)
    : line(line)
  {
  }

  void LineDirective::process(PreProcessorContext &context) const
  {
    context.report_line_number(line);
  }

  //////
  // IfSection
  void IfSection::process(PreProcessorContext &context) const
  {
    for(int i = 0;i < (int)if_groups.size();i++)
    {
      if(if_groups[i]->condition)
      {
        if(if_groups[i]->condition->evaluate(context))
        {
          if_groups[i]->process(context);

          // as soon as we processed one if block, any subsequent elif
          // or else blocks shouldn't be evaluated, so we can break out of the
          // loop right away.
          break;
        }
      }
      else
      {
        // only the last item may be one without a condition (ie.
        // it's the else block).
        assert(i == (int)if_groups.size() - 1);

        if_groups[i]->process(context);
      }
    }
  }

  /////////
  // PreProcFile
  Glib::RefPtr<PreProcFile> PreProcFile::parse(Glib::RefPtr<TokenizerInput> tokenizer_doc)
  {
    FileParser parser;
    parser.parse(tokenizer_doc);
    return parser.get_output();
  }

  Glib::RefPtr<PreProcFile> PreProcFile::parse(BuildNG::InStream &stream)
  {
    Glib::RefPtr<TokenizerInput> tokenizer_doc = TokenizerInput::create(stream);
    return parse(tokenizer_doc);
  }

  Glib::RefPtr<PreProcFile> PreProcFile::parse(const Glib::ustring &in_file_name)
  {
    BuildNG::FileInStream stream(in_file_name);
    return parse(stream);
  }

  void PreProcFile::process(PreProcessorContext &context) const
  {
    std::string dirname = Glib::path_get_dirname(path);
    context.push_current_path(dirname);

    Group::process(context);

    context.pop_current_path();
  }

  //////////
  // PreProcessorContext
  void PreProcessorContext::process_group(Glib::RefPtr<Group> group)
  {
    group->process(*this);
  }

  void PreProcessorContext::define_macro(const Glib::ustring &symbol_name)
  {
    define_macro(Glib::RefPtr<Macro>(
      new Macro(symbol_name)));
  }

  void PreProcessorContext::define_macro(const Glib::ustring &symbol_name,const Glib::ustring &value)
  {
    define_macro(Glib::RefPtr<Macro>(
      new Macro(symbol_name,value)));
  }

  void PreProcessorContext::define_macro(const Glib::ustring &symbol_name,const Glib::ustring &value,
    Glib::ArrayHandle<Glib::ustring> replacement_list)
  {
    define_macro(Glib::RefPtr<Macro>(
      new Macro(symbol_name,value,replacement_list)));
  }

  void PreProcessorContext::define_macro(Glib::RefPtr<Macro> macro)
  {
    MacrosIt it = macros.find(macro->get_symbol_name());
    if(it != macros.end())
    {
      // TODO: check the rules for redefinitions...
      assert(!"not implemented yet");
    }

    macros[macro->get_symbol_name()] = macro;
  }

  void PreProcessorContext::undef_macro(const Glib::ustring &symbol_name)
  {
    MacrosIt it = macros.find(symbol_name);
    if(it != macros.end())
      macros.erase(it);
  }

  bool PreProcessorContext::is_macro_defined(const Glib::ustring &symbol_name) const
  {
    return macros.find(symbol_name) != macros.end();
  }

  Glib::RefPtr<Macro> PreProcessorContext::get_macro(const Glib::ustring &name) const
  {
    ConstMacrosIt it = macros.find(name);
    if(it != macros.end())
      return it->second;
    else
      return Glib::RefPtr<Macro>(NULL);
  }
}

